12

问题背景

今天在 chrome devtools 中运行以下代码:

function fn (name){
  if (typeof name === 'undefined'){
     console.log('name:', name)
     let name = 'lily'
  }
}
fn()

原以为可以正常 work,实际报错:

ReferenceError: Cannot access 'name' before initialization

寻找答案

阅读 mdn let 文档,找到以下说明:

在 ECMAScript 2015 中,let 绑定不受变量提升的约束,这意味着 let 声明不会被提升到当前执行上下文的顶部。在块中的变量初始化之前,引用它将会导致 ReferenceError(而使用 var 声明变量则恰恰相反,该变量的值是 undefined )。这个变量处于从块开始到 let 初始化处理的”暂存死区“之中。

看完上面的说明,我一脸懵逼。因为以下代码可以正常运行:

function fn (name){
  if (typeof name === 'undefined'){
     console.log('name:', name)
     // let name = 'lily'
  }
}
fn() // name: undefined

也就是说,被注释掉的第四行代码对第三行是产生的影响的,既然 let 声明不会被提升,那第四行代码是怎么影响到第三行的?
思考这个问题,我想到<<javascript忍者秘籍(第二版)>>中第5章中有对创建词法环境步骤的描述,在page112中,有如下说法:

在块级环境中,仅查找当前块中通过 let 或 const 定义的变量。对于所查找到的变量。若该标识符不存在,进行注册并将其初始化为undefined。若该标识符已经存在,将保留其值。

好嘛,我更懵逼了。这个意思是 let 声明在块作用域中会被提升吧?实践是检验真理的唯一标准,在 chrome 中代码测试下:

console.log('name:', name)
let name = 'lily'

结果报错:

ReferenceError: Cannot access 'name' before initialization

这个实践无法明确说明 let 声明是否会提升的问题。我的唯一标准失效了。正当我束手无策时,突然考虑到翻译过程的误差,抱着试试看的心态查看了 mdn 文档英文版,说明如下:

let bindings are created at the top of the (block) scope containing the declaration, commonly referred to as "hoisting". Unlike variables declared with var, which will start with the value undefined, let variables are not initialized until their definition is evaluated. Accessing the variable before the initialization results in a ReferenceError. The variable is in a "temporal dead zone" from the start of the block until the initialization is processed.

这就很清晰了。原文说的是:通过 var 声明的变量有初始值 undefined,而通过 let 声明的变量直到定义的代码被执行时才会初始化。在变量初始化前访问变量会导致 ReferenceError。

总结

在上面的文档中,其实我一直误解了中文 javascript 文档中对变量提升的定义。
中文 javascript 文档中的变量提升,是指:在声明变量的代码执行之前,可以进行初始化和使用;而不是指:在创建词法环境阶段是否会创建对应的标识符。
通过 var 声明的变量和 let 或 const 声明的变量,在创建相应作用域的词法环境阶段,都会注册标识符,但仅通过 var 声明的变量存在会变量提升,若在通过 let 或 const 声明了变量的(块)作用域中,先使用再声明该变量,就会抛出错误:

ReferenceError: Cannot access 'X' before initialization

补充

不要过度依赖翻译后的文档,已更新 mdn 上这部分的中文文档。


Hola
28 声望1 粉丝